#include "lua_f_base.h"
#include <cutils/work_queue.h>
#include <cutils/mem_pool.h>
#include <cutils/list.h>
#include <cutils/pthread_locker.h>

typedef struct lua_worker {
	worker_t *worker;
	mem_pool_t lua_work_mem_pool;
} lua_worker_t;

typedef struct lua_work {
	struct dl_list head;
    work_t work;
    lua_State *L;
	lua_worker_t *lua_worker;
} lua_work_t;


#define WORKER_METABLE_NAME "CUtils.Worker.MT"
#define check_lua_worker(L) (lua_worker_t *)luaL_checkudata(L, 1, WORKER_METABLE_NAME)

static void *lua_work_mem_unit_create(mem_unit_t *arg)
{
	lua_work_t *self = (lua_work_t *)MALLOC(sizeof(lua_work_t));
	if (!self) {
		ERR("malloc fail");
		return NULL;
	}

	dl_list_init(&self->head);
	lua_State *L = lua_open();
	if (!L) {
		ERR("lua_newstate fail");
		safe_free(self);
		return NULL;
	}

	luaL_openlibs(L);
	self->L = L;
	return self;
}

static int lua_work_mem_unit_destroy(mem_unit_t *unit, void *arg)
{
	lua_work_t *self = (lua_work_t *)arg;
	lua_close(self->L);
	safe_free(self);
	return 0;
}

static mem_unit_t g_lua_work_mem_unit = {
	.total = 64,
	.size = sizeof(lua_work_t),
	.create = lua_work_mem_unit_create,
	.destroy = lua_work_mem_unit_destroy,
	.release = NULL,
	.priv = NULL,
};

static void lua_work_func(void *arg)
{
	lua_work_t *self = (lua_work_t *)arg;

    if (lua_gettop(self->L) == 1)
        lua_pcall(self->L, 0, 0, 0);
    else
        lua_pcall(self->L, 1, 0, 0);
        
	lua_worker_t *lua_worker = self->lua_worker;
	if (mem_unit_put(&lua_worker->lua_work_mem_pool, self) < 0)
		DBG("mem_unit_put fail");

	EXIT();
}

static int _lua_f_worker_add(lua_State *L, int is_tail)
{
	lua_worker_t *self = check_lua_worker(L);
	worker_t *worker = self->worker;
	uint8_t has_args = 0;

	// check argument
	if (lua_type(L, -1) != LUA_TTABLE)
		RETERR("invalid type");

    lua_getfield(L, -1, "func");
	if (lua_type(L, -1) == LUA_TNIL)
		RETERR("no func member");
	
	lua_getfield(L, -2, "args");
	if (lua_type(L, -1) == LUA_TNIL) {
		DBG("no args member\n");
		lua_pop(L, 1);
	}
	else
		has_args = 1;
	
	// new a lua_worker_t object
	lua_work_t *lua_work = (lua_work_t *)mem_unit_get(&self->lua_work_mem_pool);
	if (!lua_work)
		RETERR("mem_unit_get fail");

	// init lua_worker_t object
	lua_work->lua_worker = self;
	work_init(&lua_work->work, lua_work, lua_work_func);
	if (has_args)
	    lua_xmove(L, lua_work->L, 2);
	else
		lua_xmove(L, lua_work->L, 1);
	    
	// add lua_worker_t object into queue.
	if (is_tail)
		worker_add_tail(worker, &lua_work->work);
	else
		worker_add(worker, &lua_work->work);
	
	lua_pushinteger(L, 0);
	return 1;
}

static inline int lua_f_worker_add(lua_State *L)
{
	return _lua_f_worker_add(L, 0);
}

static inline int lua_f_worker_add_tail(lua_State *L)
{
	return _lua_f_worker_add(L, 1);
}

static int lua_f_worker_nums(lua_State *L)
{
	lua_worker_t *self = check_lua_worker(L);
	worker_t *worker = self->worker;

	lua_pushinteger(L, worker_nums(worker));
	return 1;
}

static int lua_f_worker_destroy(lua_State *L)
{
	lua_worker_t *self = check_lua_worker(L);
	worker_destroy(&self->worker);
	mem_pool_term(&self->lua_work_mem_pool);
	return 0;
}

static int lua_f_worker_new(lua_State *L)
{
	worker_t *worker = worker_new();
	if (!worker)
		RETERR("worker_new fail");

	lua_worker_t *self = (lua_worker_t *)lua_newuserdata(L, sizeof(lua_worker_t));
	if (!self) {
		worker_destroy(&worker);
		RETERR("lua_newuserdata fail");
	}

	self->worker = worker;
	luaL_getmetatable(L, WORKER_METABLE_NAME);
	lua_setmetatable(L, -2);

	self->lua_work_mem_pool.mu = &g_lua_work_mem_unit;
	if (mem_pool_init(&self->lua_work_mem_pool, pthread_locker_create()) < 0) {
		worker_destroy(&self->worker);
		RETERR("mem_pool_init fail");
	}

	return 1;
}

static int lua_f_worker_sleep(lua_State *L)
{
	int s = luaL_optint(L, 1, 0);
	if (s) {
		sleep(s);
	}

	return 0;
}

static int lua_f_worker_version(lua_State *L)
{
	const char *ver = "Worker V0.0.1";
	lua_pushstring(L, ver);
	return 1;
}

static const struct luaL_Reg lua_f_worker_func[] = {
	{"destroy", lua_f_worker_destroy},
	{"add", lua_f_worker_add},
	{"add_tail", lua_f_worker_add_tail},
	{"length", lua_f_worker_nums},
	{NULL, NULL},
};

static const struct luaL_Reg lua_f_worker_mod[] = {
	{"new", lua_f_worker_new},
	{"sleep", lua_f_worker_sleep},
	{"version", lua_f_worker_version},
	{NULL, NULL},
};

/*
 * mt = {
 *	__gc = lua_f_worker_destroy,
 *	__len = lua_f_worker_nums,
 *	__index = {
 *		destroy = lua_f_worker_destroy,
 *		add = lua_f_worker_add,
 *		add_tail = lua_f_worker_add_tail,
 *		length = lua_f_worker_nums,
 *	},
 * };
 *
 * worker = {
 *	new = lua_f_worker_new,
 *	version = lua_f_worker_version,
 * };
 */
int luaopen_worker(lua_State *L)
{
	luaL_newmetatable(L, WORKER_METABLE_NAME);
	lua_pushcfunction(L, lua_f_worker_destroy);
	lua_setfield(L, -2, "__gc");
	lua_pushcfunction(L, lua_f_worker_nums);
	lua_setfield(L, -2, "__len");
	lua_newtable(L);
	luaL_register(L, NULL, lua_f_worker_func);
	lua_setfield(L, -2, "__index");
	lua_pop(L, 1);

	luaL_register(L, "worker", lua_f_worker_mod);
	return 1;
}
